/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content.res;

import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

/**
 * 供对应用程序的原始资产文件的访问权限; 请参阅{@link Resources}了解大多数应用程序将要检索其资源数据的方式。
 * 该类提供了一个较低级别的API，允许您打开并读取已与应用程序捆绑在一起的原始文件作为简单的字节流。
 */
public final class AssetManager implements AutoCloseable {
    /* 打开asset时使用的模式 */

    /**
     * {@link #open(String, int)}的模式：没有关于如何访问数据的具体信息。
     */
    public static final int ACCESS_UNKNOWN = 0;
    /**
     * Mode for {@link #open(String, int)}: 阅读分块，并前进和后退。
     * access random
     */
    public static final int ACCESS_RANDOM = 1;
    /**
     * Mode for {@link #open(String, int)}: 按顺序阅读，偶尔向前寻求。
     * access streaming
     */
    public static final int ACCESS_STREAMING = 2;
    /**
     * Mode for {@link #open(String, int)}: 尝试将内容加载到内存中，以便快速小读取
     * access buffer
     */
    public static final int ACCESS_BUFFER = 3;

    private static final String TAG = "AssetManager";
    //是否打印
    private static final boolean localLOGV = false || false;
    //是否是测试
    private static final boolean DEBUG_REFS = false;
    //如果是测试：mNumRefs从0开始，逐渐增加，统计异常
    private int mNumRefs = 1;
    //测试使用：统计异常
    private HashMap<Long, RuntimeException> mRefStacks;
    
    private static final Object sSync = new Object();
    /*package*/
    static AssetManager sSystem = null;

    private final TypedValue mValue = new TypedValue();
    private final long[] mOffsets = new long[2];
    
    // 用于与本机代码进行通信。
    private long mObject;

    private StringBlock mStringBlocks[] = null;
    

    private boolean mOpen = true;

 
    /**
     * {@hide} 创建一个仅包含基本系统资产的新AssetManager。
     * 应用程序通常不会使用此方法，而是使用{@link Resources#getAssets}来检索相应的资产管理器。 不适用于应用程序。
     */
    public AssetManager() {
        synchronized (this) {
            if (DEBUG_REFS) {
                mNumRefs = 0;
                incRefsLocked(this.hashCode());
            }
            //native方法初始化
            init(false);
            if (localLOGV) Log.v(TAG, "New asset manager: " + this);
            ensureSystemAssets();
        }
    }

    private static void ensureSystemAssets() {
        synchronized (sSync) {
            if (sSystem == null) {
                AssetManager system = new AssetManager(true);
                system.makeStringBlocks(null);
                sSystem = system;
            }
        }
    }
    
    private AssetManager(boolean isSystem) {
        if (DEBUG_REFS) {
            synchronized (this) {
                mNumRefs = 0;
                incRefsLocked(this.hashCode());
            }
        }
        init(true);
        if (localLOGV) Log.v(TAG, "New asset manager: " + this);
    }

    /**
     * {@hide} 返回全局共享资产管理器，该管理器仅提供对系统资产的访问（无应用程序资产）
     */
    public static AssetManager getSystem() {
        ensureSystemAssets();
        return sSystem;
    }

    /**
     * 关闭这个资产管理器。
     */
    public void close() {
        synchronized(this) {
            //System.out.println("Release: num=" + mNumRefs
            //                   + ", released=" + mReleased);
            if (mOpen) {
                mOpen = false;
                decRefsLocked(this.hashCode());
            }
        }
    }

    /**
     * 检索与当前配置/外观的特定资源标识符关联的字符串值。
     */
    /*package*/ final CharSequence getResourceText(int ident) {
        synchronized (this) {
            TypedValue tmpValue = mValue;
            int block = loadResourceValue(ident, (short) 0, tmpValue, true);
            if (block >= 0) {
                if (tmpValue.type == TypedValue.TYPE_STRING) {
                    return mStringBlocks[block].get(tmpValue.data);
                }
                return tmpValue.coerceToString();
            }
        }
        return null;
    }

    /**
     * 检索与当前配置/外观的特定资源标识符关联的字符串值。
     */
    /*package*/ final CharSequence getResourceBagText(int ident, int bagEntryId) {
        synchronized (this) {
            TypedValue tmpValue = mValue;
            int block = loadResourceBagValue(ident, bagEntryId, tmpValue, true);
            if (block >= 0) {
                if (tmpValue.type == TypedValue.TYPE_STRING) {
                    return mStringBlocks[block].get(tmpValue.data);
                }
                return tmpValue.coerceToString();
            }
        }
        return null;
    }

    /**
     * 检索与特定资源标识符关联的字符串数组。
     * @param id 字符串数组的资源ID
     */
    /*package*/ final String[] getResourceStringArray(final int id) {
        String[] retArray = getArrayStringResource(id);
        return retArray;
    }


    /*package*/ final boolean getResourceValue(int ident,
                                               int density,
                                               TypedValue outValue,
                                               boolean resolveRefs)
    {
        int block = loadResourceValue(ident, (short) density, outValue, resolveRefs);
        if (block >= 0) {
            if (outValue.type != TypedValue.TYPE_STRING) {
                return true;
            }
            outValue.string = mStringBlocks[block].get(outValue.data);
            return true;
        }
        return false;
    }

    /**
     * 检索与特定资源标识符关联的文本数组。
     * @param id 字符串数组的资源ID
     */
    /*package*/ final CharSequence[] getResourceTextArray(final int id) {
        int[] rawInfoArray = getArrayStringInfo(id);
        int rawInfoArrayLen = rawInfoArray.length;
        final int infoArrayLen = rawInfoArrayLen / 2;
        int block;
        int index;
        CharSequence[] retArray = new CharSequence[infoArrayLen];
        for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) {
            block = rawInfoArray[i];
            index = rawInfoArray[i + 1];
            retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null;
        }
        return retArray;
    }
    
    /*package*/ final boolean getThemeValue(long theme, int ident,
            TypedValue outValue, boolean resolveRefs) {
        int block = loadThemeAttributeValue(theme, ident, outValue, resolveRefs);
        if (block >= 0) {
            if (outValue.type != TypedValue.TYPE_STRING) {
                return true;
            }
            StringBlock[] blocks = mStringBlocks;
            if (blocks == null) {
                ensureStringBlocks();
                blocks = mStringBlocks;
            }
            outValue.string = blocks[block].get(outValue.data);
            return true;
        }
        return false;
    }

    /*package*/ final void ensureStringBlocks() {
        if (mStringBlocks == null) {
            synchronized (this) {
                if (mStringBlocks == null) {
                    makeStringBlocks(sSystem.mStringBlocks);
                }
            }
        }
    }

    /*package*/
    final void makeStringBlocks(StringBlock[] seed) {
        final int seedNum = (seed != null) ? seed.length : 0;
        final int num = getStringBlockCount();
        mStringBlocks = new StringBlock[num];
        if (localLOGV) Log.v(TAG, "Making string blocks for " + this
                + ": " + num);
        for (int i=0; i<num; i++) {
            if (i < seedNum) {
                mStringBlocks[i] = seed[i];
            } else {
                mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
            }
        }
    }

    /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) {
        // Cookies map to string blocks starting at 1.
        return mStringBlocks[cookie - 1].get(id);
    }

    /**
     * 使用ACCESS STREAMING模式打开资产。 这提供了对作为资产与应用程序捆绑在一起的文件的访问权 - 也就是将文件放入“资产”目录。
     * 
     * @param fileName 要打开的资产的名称。 这个名字可以是分层的。
     * 
     * @see #open(String, int)
     * @see #list
     */
    public final InputStream open(String fileName) throws IOException {
        return open(fileName, ACCESS_STREAMING);
    }

    /**
     * 使用显式访问模式打开资产，返回输入流以读取其内容。 这提供了对作为资产与应用程序捆绑在一起的文件的访问权 - 也就是将文件放入“资产”目录。
     * 
     * @param fileName 要打开的资产的名称。 这个名字可以是分层的。
     * @param accessMode 用于检索数据的所需访问模式。
     * 
     * @see #ACCESS_UNKNOWN
     * @see #ACCESS_STREAMING
     * @see #ACCESS_RANDOM
     * @see #ACCESS_BUFFER
     * @see #open(String)
     * @see #list
     */
    public final InputStream open(String fileName, int accessMode)
        throws IOException {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            long asset = openAsset(fileName, accessMode);
            if (asset != 0) {
                AssetInputStream res = new AssetInputStream(asset);
                incRefsLocked(res.hashCode());
                return res;
            }
        }
        throw new FileNotFoundException("Asset file: " + fileName);
    }

    public final AssetFileDescriptor openFd(String fileName)
            throws IOException {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets);
            if (pfd != null) {
                return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
            }
        }
        throw new FileNotFoundException("Asset file: " + fileName);
    }

    /**
     * 返回给定路径中所有资产的字符串数组。
     * 
     * @param path 资产内的相对路径，即 "docs/home.html".
     * 
     * @return String []字符串数组，每个资源一个字符串。 这些文件名是相对于'路径'。 您可以通过连接'path'和返回字符串中的名称（通过File）并将其传递给open()来打开文件。
     * 
     * @see #open
     */
    public native final String[] list(String path)
        throws IOException;

    /**
     * {@hide}
     * 使用ACCESS STREAMING模式将非资产文件作为资产打开。 这提供了直接访问应用程序包中包含的所有文件（不仅是其资产）。 应用程序通常不应该使用这个。
     * 
     * @see #open(String)
     */
    public final InputStream openNonAsset(String fileName) throws IOException {
        return openNonAsset(0, fileName, ACCESS_STREAMING);
    }

    /**
     * {@hide}
     * 使用特定的访问模式将非资产文件作为资产打开。 这提供了直接访问应用程序包中包含的所有文件（不仅是其资产）。 应用程序通常不应该使用这个。
     * 
     * @see #open(String, int)
     */
    public final InputStream openNonAsset(String fileName, int accessMode)
        throws IOException {
        return openNonAsset(0, fileName, accessMode);
    }

    /**
     * {@hide}
     *在指定的包中打开一个非资产。 不适用于应用程序。
     * 
     * @param cookie 要打开的包的标识符
     * @param fileName 要检索的资产的名称。
     */
    public final InputStream openNonAsset(int cookie, String fileName)
        throws IOException {
        return openNonAsset(cookie, fileName, ACCESS_STREAMING);
    }

    /**
     * {@hide}
     * 在指定的包中打开一个非资产。 不适用于应用程序。
     * 
     * @param cookie 要打开的包的标识符。
     * @param fileName 要检索的资产的名称
     * @param accessMode 用于检索数据的所需访问模式。
     */
    public final InputStream openNonAsset(int cookie, String fileName, int accessMode)
        throws IOException {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            long asset = openNonAssetNative(cookie, fileName, accessMode);
            if (asset != 0) {
                AssetInputStream res = new AssetInputStream(asset);
                incRefsLocked(res.hashCode());
                return res;
            }
        }
        throw new FileNotFoundException("Asset absolute file: " + fileName);
    }

    public final AssetFileDescriptor openNonAssetFd(String fileName)
            throws IOException {
        return openNonAssetFd(0, fileName);
    }
    
    public final AssetFileDescriptor openNonAssetFd(int cookie,
            String fileName) throws IOException {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            ParcelFileDescriptor pfd = openNonAssetFdNative(cookie,
                    fileName, mOffsets);
            if (pfd != null) {
                return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
            }
        }
        throw new FileNotFoundException("Asset absolute file: " + fileName);
    }
    
    /**
     * 检索编译的XML文件的解析器
     * 
     * @param fileName 要检索的文件的名称。
     */
    public final XmlResourceParser openXmlResourceParser(String fileName)
            throws IOException {
        return openXmlResourceParser(0, fileName);
    }
    
    /**
     * 检索编译的XML文件的解析器。
     * 
     * @param cookie 要打开的包的标识符。
     * @param fileName 要检索的文件的名称。
     */
    public final XmlResourceParser openXmlResourceParser(int cookie,
            String fileName) throws IOException {
        XmlBlock block = openXmlBlockAsset(cookie, fileName);
        XmlResourceParser rp = block.newParser();
        block.close();
        return rp;
    }

    /**
     * {@hide}
     * 将非资产作为已编译的XML文件进行检索。 不适用于应用程序。
     * 
     * @param fileName 要检索的文件的名称。
     */
    /*package*/ final XmlBlock openXmlBlockAsset(String fileName)
            throws IOException {
        return openXmlBlockAsset(0, fileName);
    }

    /**
     * {@hide}
     * 将非资产作为已编译的XML文件进行检索。 不适用于应用程序。
     * 
     * @param cookie 要打开的包的标识符。
     * @param fileName 要检索的资产的名称。
     */
    /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
        throws IOException {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            long xmlBlock = openXmlAssetNative(cookie, fileName);
            if (xmlBlock != 0) {
                XmlBlock res = new XmlBlock(this, xmlBlock);
                incRefsLocked(res.hashCode());
                return res;
            }
        }
        throw new FileNotFoundException("Asset XML file: " + fileName);
    }

    /*package*/ void xmlBlockGone(int id) {
        synchronized (this) {
            decRefsLocked(id);
        }
    }

    /*package*/ final long createTheme() {
        synchronized (this) {
            if (!mOpen) {
                throw new RuntimeException("Assetmanager has been closed");
            }
            long res = newTheme();
            incRefsLocked(res);
            return res;
        }
    }

    /*package*/ final void releaseTheme(long theme) {
        synchronized (this) {
            deleteTheme(theme);
            decRefsLocked(theme);
        }
    }

    protected void finalize() throws Throwable {
        try {
            if (DEBUG_REFS && mNumRefs != 0) {
                Log.w(TAG, "AssetManager " + this
                        + " finalized with non-zero refs: " + mNumRefs);
                if (mRefStacks != null) {
                    for (RuntimeException e : mRefStacks.values()) {
                        Log.w(TAG, "Reference from here", e);
                    }
                }
            }
            destroy();
        } finally {
            super.finalize();
        }
    }
    
    public final class AssetInputStream extends InputStream {
        /**
         * @hide
         */
        public final int getAssetInt() {
            throw new UnsupportedOperationException();
        }
        /**
         * @hide
         */
        public final long getNativeAsset() {
            return mAsset;
        }
        private AssetInputStream(long asset)
        {
            mAsset = asset;
            mLength = getAssetLength(asset);
        }
        public final int read() throws IOException {
            return readAssetChar(mAsset);
        }
        public final boolean markSupported() {
            return true;
        }
        public final int available() throws IOException {
            long len = getAssetRemainingLength(mAsset);
            return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len;
        }
        public final void close() throws IOException {
            synchronized (AssetManager.this) {
                if (mAsset != 0) {
                    destroyAsset(mAsset);
                    mAsset = 0;
                    decRefsLocked(hashCode());
                }
            }
        }
        public final void mark(int readlimit) {
            mMarkPos = seekAsset(mAsset, 0, 0);
        }
        public final void reset() throws IOException {
            seekAsset(mAsset, mMarkPos, -1);
        }
        public final int read(byte[] b) throws IOException {
            return readAsset(mAsset, b, 0, b.length);
        }
        public final int read(byte[] b, int off, int len) throws IOException {
            return readAsset(mAsset, b, off, len);
        }
        public final long skip(long n) throws IOException {
            long pos = seekAsset(mAsset, 0, 0);
            if ((pos+n) > mLength) {
                n = mLength-pos;
            }
            if (n > 0) {
                seekAsset(mAsset, n, 0);
            }
            return n;
        }

        protected void finalize() throws Throwable
        {
            close();
        }

        private long mAsset;
        private long mLength;
        private long mMarkPos;
    }

    /**
     * {@hide} 向资产经理添加一组额外的资产。 这可以是目录或ZIP文件。 不适用于应用程序。 返回已添加资产的cookie，或失败时返回0。
     */
    public final int addAssetPath(String path) {
        synchronized (this) {
            int res = addAssetPathNative(path);
            makeStringBlocks(mStringBlocks);
            return res;
        }
    }


    /**
     * {@hide} 一次将多组资产添加到资产管理器。 有关更多信息，请参见{@link #addAssetPath(String)}。
     * 返回每个添加的资产的cookie数组，其中0表示失败;如果路径的输入数组为空，则返回null。
     */
    public final int[] addAssetPaths(String[] paths) {
        if (paths == null) {
            return null;
        }

        int[] cookies = new int[paths.length];
        for (int i = 0; i < paths.length; i++) {
            cookies[i] = addAssetPath(paths[i]);
        }

        return cookies;
    }




    
    private final void decRefsLocked(long id) {
        if (DEBUG_REFS && mRefStacks != null) {
            mRefStacks.remove(id);
        }
        mNumRefs--;
        //System.out.println("Dec streams: mNumRefs=" + mNumRefs
        //                   + " mReleased=" + mReleased);
        if (mNumRefs == 0) {
            destroy();
        }
    }
    //#############################################
    //############原生方法
    //#############################################

    private native final int addAssetPathNative(String path);

    /**
     * {@hide}添加一组资产以覆盖已添加的一组资产。 这仅适用于应用程序资源。 在执行任何Java代码之前处理系统范围的资源。
     */
    public native final int addOverlayPath(String idmapPath);

    /**
     * {@hide} 确定此资产管理器中的状态是否与文件系统上的文件保持同步。 如果返回false，则需要实例化新的Asset Manager类以查看新数据。
     */
    public native final boolean isUpToDate();

    /**
     * {@hide}改此资产管理器使用的区域设置。 不适用于应用程序。
     */
    public native final void setLocale(String locale);

    /**
     * 获取此资产管理器包含数据的区域设置。
     */
    public native final String[] getLocales();

    /**
     * {@hide}更改检索资源时使用的配置。 不适用于应用程序。
     */
    public native final void setConfiguration(int mcc, int mnc, String locale,
                                              int orientation, int touchscreen, int density, int keyboard,
                                              int keyboardHidden, int navigation, int screenWidth, int screenHeight,
                                              int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
                                              int screenLayout, int uiMode, int majorVersion);

    /**
     * 检索给定资源名称的资源标识符。
     */
    /*package*/ native final int getResourceIdentifier(String type,
                                                       String name,
                                                       String defPackage);

    /*package*/ native final String getResourceName(int resid);
    /*package*/ native final String getResourcePackageName(int resid);
    /*package*/ native final String getResourceTypeName(int resid);
    /*package*/ native final String getResourceEntryName(int resid);

    private native final long openAsset(String fileName, int accessMode);
    private final native ParcelFileDescriptor openAssetFd(String fileName,
                                                          long[] outOffsets) throws IOException;
    private native final long openNonAssetNative(int cookie, String fileName,
                                                 int accessMode);
    private native ParcelFileDescriptor openNonAssetFdNative(int cookie,
                                                             String fileName, long[] outOffsets) throws IOException;
    private native final void destroyAsset(long asset);
    private native final int readAssetChar(long asset);
    private native final int readAsset(long asset, byte[] b, int off, int len);
    private native final long seekAsset(long asset, long offset, int whence);
    private native final long getAssetLength(long asset);
    private native final long getAssetRemainingLength(long asset);

    /**如果找到资源，则返回true，填写mRetStringBlock和mRetData* */
    private native final int loadResourceValue(int ident, short density, TypedValue outValue,
                                               boolean resolve);
    /**
     * 如果找到资源，则返回true，填写mRetStringBlock和mRetData*/
    private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue,
                                                  boolean resolve);
    /*package*/ static final int STYLE_NUM_ENTRIES = 6;
    /*package*/ static final int STYLE_TYPE = 0;
    /*package*/ static final int STYLE_DATA = 1;
    /*package*/ static final int STYLE_ASSET_COOKIE = 2;
    /*package*/ static final int STYLE_RESOURCE_ID = 3;
    /*package*/ static final int STYLE_CHANGING_CONFIGURATIONS = 4;
    /*package*/ static final int STYLE_DENSITY = 5;
    /*package*/ native static final boolean applyStyle(long theme,
                                                       int defStyleAttr, int defStyleRes, long xmlParser,
                                                       int[] inAttrs, int[] outValues, int[] outIndices);
    /*package*/ native static final boolean resolveAttrs(long theme,
                                                         int defStyleAttr, int defStyleRes, int[] inValues,
                                                         int[] inAttrs, int[] outValues, int[] outIndices);
    /*package*/ native final boolean retrieveAttributes(
            long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices);
    /*package*/ native final int getArraySize(int resource);
    /*package*/ native final int retrieveArray(int resource, int[] outValues);
    private native final int getStringBlockCount();
    private native final long getNativeStringBlock(int block);

    /**
     * {@hide}
     */
    public native final String getCookieName(int cookie);

    /**
     * {@hide}
     */
    public native final SparseArray<String> getAssignedPackageIdentifiers();

    /**
     * {@hide}
     */
    public native static final int getGlobalAssetCount();

    /**
     * {@hide}
     */
    public native static final String getAssetAllocations();

    /**
     * {@hide}
     */
    public native static final int getGlobalAssetManagerCount();

    private native final long newTheme();
    private native final void deleteTheme(long theme);
    /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force);
    /*package*/ native static final void copyTheme(long dest, long source);
    /*package*/ native static final int loadThemeAttributeValue(long theme, int ident,
                                                                TypedValue outValue,
                                                                boolean resolve);
    /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix);

    private native final long openXmlAssetNative(int cookie, String fileName);

    private native final String[] getArrayStringResource(int arrayRes);
    private native final int[] getArrayStringInfo(int arrayRes);
    /*package*/ native final int[] getArrayIntResource(int arrayRes);
    /*package*/ native final int[] getStyleAttributes(int themeRes);

    private native final void init(boolean isSystem);
    private native final void destroy();


    //#############################################
    //############测试方法
    //#############################################
    private final void incRefsLocked(long id) {
        if (DEBUG_REFS) {
            if (mRefStacks == null) {
                mRefStacks = new HashMap<Long, RuntimeException>();
            }
            RuntimeException ex = new RuntimeException();
            ex.fillInStackTrace();
            mRefStacks.put(id, ex);
        }
        mNumRefs++;
    }
}
